﻿using Microsoft.SqlServer.Server;
using SketchUpAPI.NET.Geometry.Intersect;
using SketchUpAPI.NET.Objects;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SketchUpAPI.NET.Geometry
{
    /// <summary>
    /// 四边面球体
    /// </summary>
    /// <remarks>四边面球体使用六个大块拼成，可以理解为将正方体的每个面变成了一个球面</remarks>
    public class SphereQuads : IGeometry
    {
        /// <summary>
        /// 球心
        /// </summary>
        public Point3D BasePoint { get; set; }

        /// <summary>
        /// 半径
        /// </summary>
        public double Radius { get; set; }

        /// <summary>
        /// 分段数
        /// </summary>
        /// <remarks>不小于1</remarks>
        public int Segment { get; set; } = 10;

        /// <summary>
        /// 球体北极方向
        /// </summary>
        /// <remarks>指向上方大块的中心</remarks>
        public Vector3D North { get; set; } = Vector3D.UnitZ;

        private Vector3D _dirX = Vector3D.UnitX;
        /// <summary>
        /// X轴方向
        /// </summary>
        /// <remarks>指向右方大块的中心，如果该方向与 <see cref="North"/> 不垂直，将改变该方向</remarks>
        public Vector3D DirectorX
        {
            get => _dirX;
            set
            {
                Vector3D y = value.CrossProduct(North);
                _dirX = -y.CrossProduct(North).Unit;
            }
        }

        /// <summary>
        /// 创建四边面球体
        /// </summary>
        /// <param name="basePoint">球心</param>
        /// <param name="radius">半径</param>
        public SphereQuads(Point3D basePoint, double radius)
        {
            BasePoint = basePoint;
            Radius = radius;
        }

        public GeometryInput ToGeometry()
        {
            /* 创建方法：
             * 1、创建一个正方体，使得正方体的每个顶点都在球面上
             * 2、对正方体的任意一个面按照分段数分割网格，每个面应该得到 分段数的平方 个小面
             * 3、计算每个小面对应的球面
             * 4、对正方体的每个面执行步骤2和步骤3，即可得到一个球体
             */
            Vector3D x = DirectorX;
            Vector3D y = North.CrossProduct(DirectorX).Unit;
            Vector3D z = North;

            GeometryInput gi = new GeometryInput();
            AddSqure(gi, x, z, -y); // 前面
            AddSqure(gi, -x, z, y); // 后面
            AddSqure(gi, -y, z, -x); // 左面
            AddSqure(gi, y, z, x); // 右面
            AddSqure(gi, x, y, z); // 上面
            AddSqure(gi, x, -y, -z); // 下面

            return gi;
        }

        /// <summary>
        /// 添加一个大块
        /// </summary>
        /// <param name="gi"></param>
        /// <param name="x">该大块对应正方体面的x方向</param>
        /// <param name="y">该大块对应正方体面的y方向</param>
        /// <param name="normal">该大块对应正方体面的法向（朝向球外）</param>
        private void AddSqure(GeometryInput gi, Vector3D x, Vector3D y, Vector3D normal)
        {
            /* 大块处理：
             * 注意：需要保证每个小面都是平面（即保证小面的四个顶点共面）
             * 1、因为正方形是中心对称，所以只需要处理四分之一，其余均可经过镜像得到
             * 2、对于其中四分之一，首先正方形边缘上的分割点可以直接投影到球面
             * 3、假如正方向左上角的顶点的索引是（0，0），上边缘第一个分割点的索引是（0，1），左边缘的第一个顶点为（1，0）
             * 4、那么（1，1）点与球心的连线即是一条半径，改半径与平面 （0，0）、（1，0）、（0，1）的交点就是该分割点在球面上的点
             */

            Point3D[,] points = new Point3D[Segment + 1, Segment + 1]; // 创建一个用于保存该球面全部点位的二维数组

            // 获取四分之一的球面点
            CreateLeftTopQuater(points, x, y, normal);

            // 获取右上角四分之一
            GetRightTopQuater(points, x);

            // 获取下方一般
            GetBottomHalf(points, y);

            // 创建面
            for (int i = 0; i < Segment; i++) // 遍历每一行
            {
                for (int j = 0; j < Segment; j++) // 遍历一列
                {
                    Point3D lt = points[i, j];
                    Point3D rt = points[i, j + 1];
                    Point3D lb = points[i + 1, j];
                    Point3D rb = points[i + 1, j + 1];

                    int lt_index = gi.AddVertex(lt);
                    int rt_index = gi.AddVertex(rt);
                    int lb_index = gi.AddVertex(lb);
                    int rb_index = gi.AddVertex(rb);

                    gi.AddFaceWithSmoothSoft(lb_index, rb_index, rt_index, lt_index);
                }
            }
        }

        /// <summary>
        /// 计算四分之一的点
        /// </summary>
        /// <param name="points"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="normal"></param>
        private void CreateLeftTopQuater(Point3D[,] points, Vector3D x, Vector3D y, Vector3D normal)
        {
            /* 选择左上四分之一的左下一般作为初始的八分之一
             */

            double c = Math.Sqrt(Radius * Radius / 3) * 2;

            int pointCount = Segment / 2 + 1; // 如果是偶数分段，则需要包含对称轴上的点，所有都是 seg/2+1个点
            Point3D center = BasePoint + normal * c / 2;
            Point3D ptLT = center - x * c / 2 + y * c / 2;
            double space = c / Segment;

            // 添加该四分之一的初始分割点
            for (int i = 0; i < pointCount; i++)
            {
                for (int j = 0; j < pointCount; j++)
                {
                    points[i, j] = ptLT + x * space * j - y * space * i;
                }
            }

            // 计算正方形边缘的点的真实球面点
            for (int i = 0; i < pointCount; i++)
            {
                points[0, i] = ProjectToSquare(points[0, i]); // 上边缘
                points[i, 0] = ProjectToSquare(points[i, 0]); // 左边缘
            }

            // 计算其余的真实点位
            for (int i = 1; i < pointCount; i++)
            {
                for (int j = 1; j < pointCount; j++)
                {
                    // 当前小面的四个顶点
                    Point3D lt = points[i - 1, j - 1];
                    Point3D lb = points[i, j - 1];
                    Point3D rt = points[i - 1, j];
                    Point3D rb = points[i, j]; // 右下角点正是需要求的点

                    Plane3D p = Plane3D.Create(lt, lb, rt);
                    Line l = new Line(BasePoint, rb);

                    Intersection.LinePlane(l, p, out double t);
                    points[i, j] = l.PointAt(t);
                }
            }
        }

        /// <summary>
        /// 获取右上角四分之一
        /// </summary>
        /// <param name="points"></param>
        /// <param name=""></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="normal"></param>
        private void GetRightTopQuater(Point3D[,] points, Vector3D x)
        {
            Plane3D plane = Plane3D.Create(BasePoint, x);

            // 做镜像时，由于对称轴的镜像点就是他自己，所有是否遍历都可以
            for (int i = 0; i < Segment / 2 + 1; i++) // 遍历前一半行数
            {
                for (int j = Segment; j > Segment / 2; j--) // 遍历后一半列数
                {
                    points[i, j] = plane.Mirror(points[i, Segment - j]);
                }
            }
        }

        /// <summary>
        /// 获取下侧一半
        /// </summary>
        /// <param name="points"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="normal"></param>
        private void GetBottomHalf(Point3D[,] points, Vector3D y)
        {
            Plane3D plane = Plane3D.Create(BasePoint, y);

            // 做镜像时，由于对称轴的镜像点就是他自己，所有是否遍历都可以
            for (int i = Segment; i > Segment / 2; i--) // 遍历后一半行数
            {
                for (int j = 0; j < Segment + 1; j++) // 遍历全部列数
                {
                    points[i, j] = plane.Mirror(points[Segment - i, j]);
                }
            }
        }

        /// <summary>
        /// 将点投影到球面上
        /// </summary>
        /// <param name="pt"></param>
        /// <param name="scale">缩放</param>
        /// <returns></returns>
        private Point3D ProjectToSquare(Point3D pt)
        {
            Vector3D v = pt - BasePoint;
            return BasePoint + v.Unit * Radius;
        }
    }
}